#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Net.Mail;
using NUnit.Framework;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.ObjectModel;
using Utilities.Reflection;
using Utilities.Testing;

namespace Utilities.Test.Reflection
{
  public class ReflectionUtilsFixture : TestFixtureBase
  {
    public abstract class AbstractDefaultConstructor
    {
      private int _dummy;

      public int Dummy
      {
        get { return _dummy; }
        set { _dummy = value; }
      }

      public AbstractDefaultConstructor()
      {
        _dummy = int.MaxValue;
      }
    }

    #region IsInstantiatableType
    [Test]
    public void IsInstantiatableType()
    {
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(string)));
      Assert.IsTrue(ReflectionUtils.IsInstantiatableType(typeof(Version)));
      Assert.IsTrue(ReflectionUtils.IsInstantiatableType(typeof(int)));
      Assert.IsTrue(ReflectionUtils.IsInstantiatableType(typeof(List<string>)));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(void)));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(Nullable<>)));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(List<>)));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(int[])));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(ICloneable)));
      Assert.IsFalse(ReflectionUtils.IsInstantiatableType(typeof(AbstractDefaultConstructor)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsInstantiatableTypeNull()
    {
      ReflectionUtils.IsInstantiatableType(null);
    }
    #endregion

    #region HasDefaultConstructor
    [Test]
    public void HasDefaultConstructor()
    {
      Assert.IsFalse(ReflectionUtils.HasDefaultConstructor(typeof(string)));
      Assert.IsTrue(ReflectionUtils.HasDefaultConstructor(typeof(Version)));
      Assert.IsTrue(ReflectionUtils.HasDefaultConstructor(typeof(int)));
      Assert.IsFalse(ReflectionUtils.HasDefaultConstructor(typeof(int[])));
      Assert.IsFalse(ReflectionUtils.HasDefaultConstructor(typeof(ICloneable)));
      Assert.IsTrue(ReflectionUtils.HasDefaultConstructor(typeof(AbstractDefaultConstructor)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void HasDefaultConstructorNull()
    {
      ReflectionUtils.HasDefaultConstructor(null);
    }
    #endregion

    [Test]
    public void IsNullable()
    {
      Assert.IsTrue(ReflectionUtils.IsNullable(typeof(string)));
      Assert.IsTrue(ReflectionUtils.IsNullable(typeof(ICloneable)));
      Assert.IsTrue(ReflectionUtils.IsNullable(typeof(Stream)));
      Assert.IsTrue(ReflectionUtils.IsNullable(typeof(int?)));
      Assert.IsFalse(ReflectionUtils.IsNullable(typeof(int)));
      Assert.IsFalse(ReflectionUtils.IsNullable(typeof(DateTime)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsNullableNull()
    {
      ReflectionUtils.IsNullable(null);
    }

    [Test]
    public void IsNullableType()
    {
      Assert.IsFalse(ReflectionUtils.IsNullableType(typeof(string)));
      Assert.IsFalse(ReflectionUtils.IsNullableType(typeof(ICloneable)));
      Assert.IsFalse(ReflectionUtils.IsNullableType(typeof(Stream)));
      Assert.IsTrue(ReflectionUtils.IsNullableType(typeof(int?)));
      Assert.IsFalse(ReflectionUtils.IsNullableType(typeof(int)));
      Assert.IsFalse(ReflectionUtils.IsNullableType(typeof(DateTime)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void IsNullableNullType()
    {
      ReflectionUtils.IsNullableType(null);
    }

    [Test]
    public void IsUnitializedValue()
    {
      Assert.IsTrue(ReflectionUtils.IsUnitializedValue(null));
      Assert.IsTrue(ReflectionUtils.IsUnitializedValue(0));
      Assert.IsTrue(ReflectionUtils.IsUnitializedValue(new DateTime()));
      Assert.IsTrue(ReflectionUtils.IsUnitializedValue(StringComparison.CurrentCulture));
      Assert.IsFalse(ReflectionUtils.IsUnitializedValue(int.MinValue));
      Assert.IsFalse(ReflectionUtils.IsUnitializedValue(new object()));
      Assert.IsFalse(ReflectionUtils.IsUnitializedValue(StringComparison.InvariantCultureIgnoreCase));
      IConvertible c = null;
      Assert.IsTrue(ReflectionUtils.IsUnitializedValue(c));
    }

    [Test]
    public void CreateUnitializedValue()
    {
      Assert.AreEqual(0, ReflectionUtils.CreateUnitializedValue(typeof(int)));
      Assert.AreEqual(0, ReflectionUtils.CreateUnitializedValue(typeof(decimal)));
      Assert.AreEqual(null, ReflectionUtils.CreateUnitializedValue(typeof(object)));
      Assert.AreEqual(null, ReflectionUtils.CreateUnitializedValue(typeof(ICloneable)));
      Assert.AreEqual(null, ReflectionUtils.CreateUnitializedValue(typeof(void)));
      Assert.AreEqual(StringComparison.CurrentCulture, ReflectionUtils.CreateUnitializedValue(typeof(StringComparison)));
      Assert.AreEqual(null, ReflectionUtils.CreateUnitializedValue(typeof(List<int>)));
      Assert.AreEqual(Guid.Empty, ReflectionUtils.CreateUnitializedValue(typeof(Guid)));
      Assert.AreEqual(DateTime.MinValue, ReflectionUtils.CreateUnitializedValue(typeof(DateTime)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentException), ExpectedMessage = @"Type System.Collections.Generic.List`1[T] is a generic type definition and cannot be instantiated.
Parameter name: type")]
    public void CreateUnitializedValueGenericTypeDefinition()
    {
      ReflectionUtils.CreateUnitializedValue(typeof(List<>));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void CreateUnitializedValueNullType()
    {
      ReflectionUtils.CreateUnitializedValue(null);
    }

    [Test]
    public void GetTypeNameWithAssessmblyName()
    {
      Assert.AreEqual("Utilities.Test.Reflection.ReflectionUtilsFixture, Utilities.Test", ReflectionUtils.GetNameAndAssessmblyName(typeof(ReflectionUtilsFixture)));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: t")]
    public void GetTypeNameWithAssessmblyNameTypeNull()
    {
      ReflectionUtils.GetNameAndAssessmblyName(null);
    }

    #region FindMemberTestClasses
    public class BaseClass
    {
      public string PublicBaseField;
      private string _privateBaseField;

      public string PublicBaseProperty
      {
        get { return _privateBaseField; }
        set { _privateBaseField = value; }
      }
    }

    public class InheritedClass : BaseClass
    {
      public string PublicInheritedField;
      private string _privateInheritedField;

      public string PublicInheritedProperty
      {
        get { return _privateInheritedField; }
        set { _privateInheritedField = value; }
      }
    }
    #endregion

    [Test]
    public void GetFields()
    {
      List<FieldInfo> fields;

      fields = ReflectionUtils.GetFields(typeof(InheritedClass), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
      Assert.AreEqual(4, fields.Count);
      CollectionAssert.AllItemsAreUnique(fields);

      fields = ReflectionUtils.GetFields(typeof(InheritedClass));
      Assert.AreEqual(4, fields.Count);
      CollectionAssert.AllItemsAreUnique(fields);

      fields = ReflectionUtils.GetFields(typeof(InheritedClass), BindingFlags.NonPublic | BindingFlags.Instance);
      Assert.AreEqual(2, fields.Count);
      CollectionAssert.AllItemsAreUnique(fields);

      fields = ReflectionUtils.GetFields(typeof(InheritedClass), BindingFlags.Public | BindingFlags.Instance);
      Assert.AreEqual(2, fields.Count);
      CollectionAssert.AllItemsAreUnique(fields);
    }

    [Test]
    public void FindMembers()
    {
      List<MemberInfo> members;

      members = ReflectionUtils.FindMembers(typeof(InheritedClass), MemberTypes.Field, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, null);
      Assert.AreEqual(4, members.Count);
      CollectionAssert.AllItemsAreUnique(members);

      members = ReflectionUtils.FindMembers(typeof(InheritedClass), MemberTypes.All, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, null);
      Assert.AreEqual(17, members.Count);
      CollectionAssert.AllItemsAreUnique(members);
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: targetType")]
    public void FindMembersNullTarget()
    {
      ReflectionUtils.FindMembers(null, MemberTypes.All, BindingFlags.Default, null, null);
    }

    #region IsSubClass
    [Test]
    public void IsSubClass()
    {
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(string), typeof(object)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(int), typeof(object)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(ValueType), typeof(object)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(Version), typeof(object)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(int), typeof(ValueType)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(StringComparison), typeof(ValueType)));
      Assert.IsFalse(ReflectionUtils.IsSubClass(typeof(object), typeof(ICloneable)));
      Assert.IsFalse(ReflectionUtils.IsSubClass(typeof(ValueType), typeof(StringComparison)));
      Assert.IsFalse(ReflectionUtils.IsSubClass(typeof(Type), typeof(int)));
      Assert.IsFalse(ReflectionUtils.IsSubClass(typeof(ICloneable), typeof(object)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(int), typeof(int)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(Nullable<int>), typeof(Nullable<>)));
      Assert.IsFalse(ReflectionUtils.IsSubClass(typeof(Nullable<>), typeof(Nullable<int>)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(Nullable<int>), typeof(Nullable<int>)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(Predicate<string>), typeof(Predicate<>)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(EqualityComparer<string>), typeof(IEqualityComparer<>)));
      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(EqualityComparer<string>), typeof(IEqualityComparer)));
    }

    [Test]
    public void IsSubClassImplementingType()
    {
      Type implementingType;

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(string), typeof(object), out implementingType));
      Assert.IsInstanceOfType(typeof(object), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(AttachmentCollection), typeof(ICollection<>), out implementingType));
      Assert.AreEqual(typeof(ICollection<Attachment>), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(EqualityComparer<string>), typeof(IEqualityComparer<>), out implementingType));
      Assert.AreEqual(typeof(IEqualityComparer<string>), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(EqualityComparer<string>), typeof(IEqualityComparer), out implementingType));
      Assert.AreEqual(typeof(EqualityComparer<string>), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(AttachmentCollection), typeof(Collection<>), out implementingType));
      Assert.AreEqual(typeof(Collection<Attachment>), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(Collection<>), typeof(Collection<>), out implementingType));
      Assert.AreEqual(typeof(Collection<>), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(IEqualityComparer), typeof(IEqualityComparer), out implementingType));
      Assert.AreEqual(typeof(IEqualityComparer), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(int), typeof(ValueType), out implementingType));
      Assert.AreEqual(typeof(ValueType), implementingType);

      Assert.IsTrue(ReflectionUtils.IsSubClass(typeof(IList<int>), typeof(ICollection<>), out implementingType));
      Assert.AreEqual(typeof(ICollection<int>), implementingType);
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void IsSubClassNullType()
    {
      ReflectionUtils.IsSubClass(null, typeof(object));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: check")]
    public void IsSubClassNullCheck()
    {
      ReflectionUtils.IsSubClass(typeof(object), null);
    }
    #endregion

    #region GetListItemType
    [Test]
    public void GetTypedListItemType()
    {
      Assert.AreEqual(typeof(int), ReflectionUtils.GetListItemType(typeof(IList<int>)));
      Assert.AreEqual(typeof(int), ReflectionUtils.GetListItemType(typeof(int[])));
      Assert.AreEqual(typeof(Attachment), ReflectionUtils.GetListItemType(typeof(AttachmentCollection)));
      Assert.AreEqual(typeof(List<Version>), ReflectionUtils.GetListItemType(typeof(List<List<Version>>)));
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = "Type System.Collections.Generic.IList`1[T] is not a list.")]
    public void GetTypedListItemTypeGenericDefinition()
    {
      ReflectionUtils.GetListItemType(typeof(IList<>));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void GetTypedListItemTypeNull()
    {
      ReflectionUtils.GetListItemType(null);
    }

    [Test]
    public void GetTypedListItemTypeArrayList()
    {
      Type arrayListType = ReflectionUtils.GetListItemType(typeof(ArrayList));
      Assert.AreEqual(null, arrayListType);
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = @"Type System.Object is not a list.")]
    public void GetTypedListItemTypeObject()
    {
      ReflectionUtils.GetListItemType(typeof(object));
    }
    #endregion

    #region GetDictionaryValueType
    public class TestGenericDictionary : Dictionary<string, Version>
    {
    }

    [Test]
    public void GetTypedDictionaryValueType()
    {
      Assert.AreEqual(typeof(int), ReflectionUtils.GetDictionaryValueType(typeof(IDictionary<int, int>)));
      Assert.AreEqual(typeof(object), ReflectionUtils.GetDictionaryValueType(typeof(Dictionary<int, object>)));
      Assert.AreEqual(typeof(object), ReflectionUtils.GetDictionaryValueType(typeof(Dictionary<int, object>)));
      Assert.AreEqual(typeof(Version), ReflectionUtils.GetDictionaryValueType(typeof(TestGenericDictionary)));
      Assert.AreEqual(typeof(IDictionary<Type, Type>), ReflectionUtils.GetDictionaryValueType(typeof(Dictionary<int, IDictionary<Type, Type>>)));
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = "Type System.Collections.Generic.IDictionary`2[TKey,TValue] is not a dictionary.")]
    public void GetTypedDictionaryValueTypeGenericDefinition()
    {
      ReflectionUtils.GetDictionaryValueType(typeof(IDictionary<,>));
    }

    [Test]
    [ExpectedException(typeof(ArgumentNullException), ExpectedMessage = @"Value cannot be null.
Parameter name: type")]
    public void GetTypedDictionaryValueTypeNull()
    {
      ReflectionUtils.GetDictionaryValueType(null);
    }

    [Test]
    public void GetTypedDictionaryValueTypeHashtable()
    {
      Type hashtableValueType = ReflectionUtils.GetDictionaryValueType(typeof(Hashtable));
      Assert.AreEqual(null, hashtableValueType);
    }

    [Test]
    [ExpectedException(typeof(Exception), ExpectedMessage = @"Type System.Object is not a dictionary.")]
    public void GetTypedDictionaryValueTypeObject()
    {
      ReflectionUtils.GetDictionaryValueType(typeof(object));
    }
    #endregion
  }
}